import type { ElementLocator } from '../../../element/service/types'
import type {
  ElementConfig,
  ElementDealOptions,
  ElementSaveType,
  ElementSelectorCompoundOptions,
  ElementTagConfig,
  HideElementOptions,
} from '../../../types/constants'
import {
  BodyAttrs,
  DEFAULT_ELEMENT_CONFIG_VALUES,
  ELEMENT_HIDE_TYPE,
  ELEMENT_SAVE_CONFIG,
  ELEMENT_SAVE_SEPARATOR,
  ELEMENT_WHITE_SELECTORS,
  ElementConfigSource,
  ElementSelectorCompoundQueryScope,
  ElementSelectorDOMScopeType,
  ElementSelectorType,
  logger,
  loggerDeduplicator,
  pageJQuery,
} from '../../../types/constants'
import { queryHTMLElement, queryJQuery } from '../../element_query'
import { DomUtils } from '../base/dom'
import { FrameworkType } from '../base/environment'

/**
 * 元素工具类
 */
export class ElementUtils {
  /**
   * 判断是否为 jQuery 对象
   */
  static isJQueryObject(obj: any): obj is JQuery<HTMLElement> {
    return obj && typeof obj === 'object' && 'jquery' in obj && typeof obj.length === 'number'
  }

  /**
   * 根据选择器获取元素
   * @param element 元素配置
   * @returns 元素
   */
  static getElement(element: ElementConfig | ElementTagConfig): JQuery<HTMLElement> | undefined {
    const selector = 'selector' in element ? element.selector : ''
    return $(selector)
  }

  /**
   * 获取元素的直接文本节点（不包括子元素的文本）
   * @private
   * @param {Element} element - DOM元素
   * @returns {Node[]} 直接文本节点数组
   */
  static getDirectTextNodes(element: Element): Node[] {
    const textNodes: Node[] = []
    const childNodes = element.childNodes

    for (let i = 0; i < childNodes.length; i++) {
      const node = childNodes[i]
      if (node.nodeType === Node.TEXT_NODE && node.nodeValue?.trim()) {
        textNodes.push(node)
      }
    }

    return textNodes
  }

  /**
   * 获取元素的文本内容
   */
  static getElementText(
    $element: JQuery,
    options: {
      keepLineBreaks?: boolean // 是否保留换行符
      trim?: boolean // 是否修剪空白
      excludeTags?: string[] // 要排除的标签名
    } = {},
  ): string {
    const {
      keepLineBreaks = true,
      trim = true,
      excludeTags = ['script', 'style', 'noscript'],
    } = options

    const text = $element
      .contents()
      .filter(function () {
        const isElement = this.nodeType === Node.ELEMENT_NODE
        const isText = this.nodeType === Node.TEXT_NODE

        // 文本节点直接保留
        if (isText) {
          return true
        }
        // 元素节点检查是否在排除列表中
        if (isElement) {
          return !excludeTags.includes((this as HTMLElement).tagName.toLowerCase())
        }
        return false
      })
      .map(function () {
        if (this.nodeType === Node.TEXT_NODE) {
          return keepLineBreaks
            ? this.nodeValue
            : this.nodeValue?.replace(/[\r\n]+/g, ' ')
        }
        return $(this).text()
      })
      .get()
      .join('')

    return trim ? text.trim() : text
  }

  /**
   * 检查元素是否无子元素
   * @param $element jQuery对象
   */
  static hasNoChildren($element: JQuery<HTMLElement>): boolean {
    return !$element || !$element.length || $element.children().length === 0
  }

  /**
   * 获取元素的伪元素 content 值
   * @param {jQuery} $el - jQuery元素对象
   * @param {string} type - 伪元素类型 ('before' 或 'after')
   * @returns {string} 伪元素的content属性值
   */
  static getPseudoContent($el: JQuery<HTMLElement>, type: 'before' | 'after'): string {
    // 转换为原生DOM元素
    const el = $el.get(0)

    if (!el)
      return ''

    // 获取计算样式
    return window.getComputedStyle(el, `::${type}`).content
  }

  /**
   * 解析元素的类名列表
   */
  static parseElementClassList($element: JQuery<HTMLElement>): string[] {
    return $element.attr('class')
      ?.split(' ')
      .filter(Boolean)
      .filter(className => className !== '\n')
      .map(className => className.trim()) || []
  }

  /**
   * 生成标准化类选择器
   * @param tag
   */
  static getTagSelector(tag: string): string {
    const className = $(tag).attr('class')

    if (!className) {
      loggerDeduplicator.warn(`元素标签 ${tag} 没有 class 属性`)
      return ''
    }

    return `.${className.split(' ').join('.')}`
  }

  /**
   * 验证选择器类型是否有效
   * @param config 选择器配置
   * @returns 是否有效
   */
  static validateSelectorType(config: ElementConfig): boolean {
    const { selectorType, selector } = config

    if (!Object.values(ElementSelectorType).includes(selectorType)) {
      logger.error(`无效选择器类型: ${selectorType}`)
      return false
    }

    [
      ElementSelectorType.Id,
      ElementSelectorType.Class,
      ElementSelectorType.Tag,
      ElementSelectorType.Attribute,
    ].forEach((type) => {
      if (selectorType === type && selector && this.detectSelectorType(selector) !== type) {
        logger.error(`无效 ${type} 选择器格式: ${selector}`)
        return false
      }
    })

    return true
  }

  /**
   * 检测选择器类型
   */
  static detectSelectorType(selector: string): ElementSelectorType {
    const cleanedSelector = selector.trim()

    // ID 选择器：以 # 开头，不包含空格和特殊字符
    if (cleanedSelector.match(/^#[\w-]+$/)) {
      return ElementSelectorType.Id
    }

    // 类选择器：以 . 开头，不包含空格和特殊字符
    if (cleanedSelector.match(/^\.[\w-]+$/)) {
      return ElementSelectorType.Class
    }

    // 标签选择器：纯字母，不包含特殊字符
    if (cleanedSelector.match(/^[a-z]+$/i)) {
      return ElementSelectorType.Tag
    }

    // jQuery 选择器：包含特殊 jQuery 伪类或方法
    if (this.detectJQuerySelector(cleanedSelector)) {
      return ElementSelectorType.JQuery
    }

    // 复合选择器：标签+类、标签+ID等
    if (cleanedSelector.match(/^[a-z][\w-]*\.[\w-]+$/i) // div.class
      || cleanedSelector.match(/^[a-z][\w-]*#[\w-]+$/i) // div#id
      || cleanedSelector.match(/^\.[\w-]+\.[\w-]+$/)) { // .class1.class2
      return ElementSelectorType.Mix
    }

    // 多选择器：包含逗号
    if (cleanedSelector.includes(',')) {
      return ElementSelectorType.Multiple
    }

    // 混合选择器：包含空格但不包含复杂关系符
    if (cleanedSelector.includes(' ') && !cleanedSelector.match(/[>+~]/)) {
      return ElementSelectorType.Mix
    }

    // 复杂选择器：包含关系符
    if (cleanedSelector.match(/[>+~]/) || cleanedSelector.includes(':')) {
      return ElementSelectorType.Complex
    }

    // 属性选择器：包含 [], 例如：[name="value"]、'div[data-id="123"]'、button[type="submit"]、[data-id="123"].active
    if (cleanedSelector.includes('[') && cleanedSelector.includes(']')) {
      return ElementSelectorType.Attribute
    }

    // 默认为标签选择器
    return ElementSelectorType.JQuery
  }

  /**
   * 检测 jQuery 选择器
   */
  static detectJQuerySelector(selector: string): ElementSelectorType | null {
    const jqueryPatterns: RegExp[] = [
      /:contains\(["']?([^"']+)["']?\)/,
    ]

    for (const pattern of jqueryPatterns) {
      if (pattern.test(selector)) {
        return ElementSelectorType.JQuery
      }
    }

    return null
  }

  /**
   * 生成框架特定的深度选择器
   */
  static generateDeepSelector(selector: string, framework: FrameworkType): string {
    const deepSelectors: Record<FrameworkType, string> = {
      [FrameworkType.Vue]: `:deep(${selector})`,
      [FrameworkType.Angular]: `::ng-deep ${selector}`,
      [FrameworkType.Svelte]: `:global(${selector})`,
      [FrameworkType.React]: selector,
      [FrameworkType.Solid]: selector,
    }

    return deepSelectors[framework] || selector
  }

  /**
   * 生成带命名空间的事件名称
   * @param eventType 事件类型（如click、scroll等）
   * @param namespace 命名空间
   * @param shouldUnique 是否添加唯一标识（避免事件冲突）
   * @returns 生成的事件名称
   */
  static generateEventName(eventType: string, namespace: string, shouldUnique: boolean = false): string {
    return `${eventType}.${namespace}${shouldUnique ? `-${Date.now()}` : ''}`
  }

  /**
   * 检查元素是否在指定列表中（包括祖先元素检查）
   * @param element 要检查的元素
   * @param selectors 选择器列表（白名单/黑名单）
   */
  static isInSelectorList(element: HTMLElement | JQuery<HTMLElement>, selectors: string[] | undefined): boolean {
    if (!selectors || selectors.length === 0)
      return false

    const nativeElement = this.isJQueryObject(element) ? element[0] : element

    if (!nativeElement)
      return false

    return selectors.some((selector) => {
      try {
        // 检查元素本身是否匹配
        if (nativeElement.matches && nativeElement.matches(selector)) {
          return true
        }

        // 检查祖先元素是否匹配
        let parent = nativeElement.parentElement
        while (parent) {
          if (parent.matches && parent.matches(selector)) {
            return true
          }
          parent = parent.parentElement
        }

        return false
      }
      catch (error) {
        // 处理无效的选择器
        logger.warn(`[SelectorCheck] 无效的选择器: ${selector}`, error)
        return false
      }
    })
  }

  /**
   * 获取复合选择器的目标元素
   * @param element 元素
   * @param compoundOptions 复合选择器配置
   * @returns 目标元素
   */
  static getCompoundOptionsTargets(element: JQuery<HTMLElement>, compoundOptions: ElementSelectorCompoundOptions): JQuery<HTMLElement> {
    const { attr, value, scope = ElementSelectorCompoundQueryScope.Siblings } = compoundOptions
    const selector = `[${attr}="${value}"]`

    let $targets: JQuery<HTMLElement>

    switch (scope) {
      case ElementSelectorCompoundQueryScope.Siblings:
        // 兄弟元素（不包括自身）
        $targets = element.siblings(selector)
        break
      case ElementSelectorCompoundQueryScope.All:
        // 所有元素（全局搜索）
        $targets = $(selector)
        break

      case ElementSelectorCompoundQueryScope.Children:
        // 直接子元素
        $targets = element.children(selector)
        break

      case ElementSelectorCompoundQueryScope.Descendants:
        // 后代元素（所有层级）
        $targets = element.find(selector)
        break

      case ElementSelectorCompoundQueryScope.Parent:
        // 父元素
        $targets = element.parent().filter(selector)
        break

      case ElementSelectorCompoundQueryScope.Ancestors:
        // 祖先元素（所有父级）
        $targets = element.parents(selector)
        break

      case ElementSelectorCompoundQueryScope.Next:
        // 下一个兄弟元素
        $targets = element.nextAll(selector).first()
        break

      case ElementSelectorCompoundQueryScope.Previous:
        // 上一个兄弟元素
        $targets = element.prevAll(selector).first()
        break

      case ElementSelectorCompoundQueryScope.Closest:
        // 最近的匹配元素（包括自身）
        $targets = element.closest(selector)
        break

      case ElementSelectorCompoundQueryScope.AllSiblings:
        // 所有兄弟元素（包括自身）
        $targets = element.siblings(selector).add(element.filter(selector))
        break

      case ElementSelectorCompoundQueryScope.NextSiblings:
        // 直到匹配选择器的所有下一个兄弟元素
        $targets = element.nextUntil(selector)
        break

      case ElementSelectorCompoundQueryScope.PreviousUntil:
        // 直到匹配选择器的所有上一个兄弟元素
        $targets = element.prevUntil(selector)
        break

      case ElementSelectorCompoundQueryScope.ParentsUntil:
        // 直到匹配选择器的所有祖先元素
        $targets = element.parentsUntil(selector)
        break

      default:
        // 默认使用兄弟元素
        $targets = element.siblings(selector)
    }

    return $targets
  }

  /**
   * 隐藏元素
   * @param selector 选择器
   * @param options 选项
   */
  static hideElement(selector: string, options: HideElementOptions = {}): void {
    const {
      domScope = ElementSelectorDOMScopeType.Document,
      hostSelector = '',
      clearChildStyles = false,
      compoundOptions = undefined,
      type = ELEMENT_HIDE_TYPE.ENGINE,
      whiteList = ELEMENT_WHITE_SELECTORS,
      checkAncestors = true,
    } = options

    const $element = queryJQuery(selector, { domScope, hostSelector })

    if (!$element || $element.length === 0) {
      logger.debug(`[${type}] [${domScope}] 元素不存在: ${selector} `, {
        hostSelector,
      })
      return
    }

    logger.debug(`[${type}] [${domScope}] 隐藏元素: ${selector}`, {
      hostSelector,
    })

    $element.each((_, element) => {
      const el = $(element)

      // 检查白名单（包括祖先元素）
      if (checkAncestors && this.isInSelectorList(element, whiteList)) {
        // 跳过白名单元素
        return
      }

      // 应用隐藏样式
      DomUtils.showControl(el, { show: false, type })

      // 清除子元素样式
      if (clearChildStyles) {
        el.find('*').not(whiteList.join(',')).css({
          display: '',
          width: '',
        })
      }

      // 处理复合选择器
      if (compoundOptions) {
        const $targets = this.getCompoundOptionsTargets(el, compoundOptions)
        DomUtils.showControl($targets, { show: false, type })
      }
    })
  }

  /**
   * 显示元素
   * @param selector 选择器
   * @param options 选项
   */
  static showElement(selector: string, options: Omit<HideElementOptions, 'type'>): void {
    const {
      domScope = ElementSelectorDOMScopeType.Document,
      hostSelector = DEFAULT_ELEMENT_CONFIG_VALUES.hostSelector || '',
      clearChildStyles = DEFAULT_ELEMENT_CONFIG_VALUES.clearChildStyles,
      compoundOptions = DEFAULT_ELEMENT_CONFIG_VALUES.compoundOptions,
      whiteList = ELEMENT_WHITE_SELECTORS,
      checkAncestors = true,
    } = options

    const $element = queryJQuery(selector, { domScope, hostSelector })
    $element.each((_, element) => {
      const el = $(element)

      // 检查白名单（包括祖先元素）
      if (checkAncestors && this.isInSelectorList(element, whiteList)) {
        // 跳过白名单元素
        return
      }

      // 移除隐藏样式
      DomUtils.showControl(el, { show: true })

      // 恢复子元素样式，解决父级隐藏导致子级内联样式污染问题
      if (clearChildStyles) {
        el.find('*').not(whiteList.join(',')).css({
          display: '',
          width: '',
        })
      }

      // 处理复合选择器
      if (compoundOptions) {
        const $targets = this.getCompoundOptionsTargets(el, compoundOptions)
        DomUtils.showControl($targets, { show: true })
      }
    })
  }

  /**
   * 判断元素集合是否存在于DOM中（不检查可见性）
   */
  static hasElements($elements: JQuery<HTMLElement>): boolean {
    return $elements && $elements.length > 0
  }

  /**
   * 判断所有元素是否都处于活动状态（全可见或者全隐藏）
   */
  static areAllActive($elements: JQuery<HTMLElement>): boolean {
    return this.areAllVisible($elements) || this.areAllHidden($elements)
  }

  /**
   * 判断所有元素是否完全可见（全可见）
   */
  static areAllVisible($elements: JQuery<HTMLElement>): boolean {
    return $elements.length > 0 && $elements
      .filter(':visible')
      .length === $elements.length
  }

  /**
   * 判断所有元素是否都应用了可控隐藏属性（全隐藏）
   */
  static areAllHidden($elements: JQuery<HTMLElement>): boolean {
    return $elements.length > 0 && $elements
      .filter(`[${BodyAttrs.HIDE_TYPE}]:hidden`)
      .length === $elements.length
  }

  /**
   * 判断元素是否通过 CSS 隐藏
   */
  static isHideStyleApplied(locator: ElementLocator, returnTrueIfNotExists: boolean = false): boolean {
    const { domScope, hostSelector, selector } = locator
    const element = queryHTMLElement(selector, { domScope, hostSelector })

    if (!element)
      return returnTrueIfNotExists

    const computedStyle = getComputedStyle(element)

    return computedStyle.display === 'none'
  }

  /**
   * 获取元素配置
   * @param elementAlias 元素别名
   * @returns 元素配置
   */
  static getElementConfig(elementAlias: string): { elementType: ElementSaveType, enName: string, selector: string } {
    const [elementType, elementName, elementSelector] = elementAlias.split(ELEMENT_SAVE_SEPARATOR)

    let selector = ''

    if (elementType === ELEMENT_SAVE_CONFIG.ELEMENT.id) {
      selector = elementSelector
    }
    else if (elementType === ELEMENT_SAVE_CONFIG.TAG.id) {
      selector = this.getTagSelector(elementSelector)
    }
    return { elementType, enName: elementName, selector }
  }

  /**
   * 处理元素可见性
   * @param options 选项
   * @param _print 是否打印
   */
  static dealElementVisible(options: ElementDealOptions, _print: boolean): void {
    const {
      type,
      domScope,
      hostSelector,
      selector,
      hide = false,
      clearChildStyles = true,
      compoundOptions = undefined,
      source = ElementConfigSource.System,
    } = options

    if (hide) {
      if (domScope === ElementSelectorDOMScopeType.ShadowRoot)
        return

      logger.added(`[${source}] [static] [${type}] ${selector}`)
      this.hideElement(selector, { domScope, hostSelector, clearChildStyles, compoundOptions })
    }
    else {
      logger.removed(`[${source}] [static] [${type}] ${selector}`)
      this.showElement(selector, { domScope, hostSelector, clearChildStyles, compoundOptions })
    }
  }

  /**
   * 获取页面元素的原生事件列表
   */
  static getElementEvents($element: JQuery<HTMLElement>): any {
    return pageJQuery._data($element[0], 'events')
  }

  /**
   * 判断元素是否绑定了 click 事件
   */
  static isElementBindClick($element: JQuery<HTMLElement>): boolean {
    const events = this.getElementEvents($element)
    return events?.click?.length > 0
  }

  /**
   * 检查节点是否包含指定的类名
   */
  static isElementWithClasses(node: Node, classNames: string[], mode: 'some' | 'every' = 'every'): boolean {
    if (node.nodeType !== Node.ELEMENT_NODE)
      return false

    const element = node as Element

    // 根据模式选择检查方式
    switch (mode) {
      case 'some':
        return classNames.some(className => element.classList.contains(className))
      case 'every':
        return classNames.every(className => element.classList.contains(className))
      default:
        throw new Error(`Invalid mode: ${mode}. Use 'some' or 'every'.`)
    }
  }

  /**
   * 生成链接元素
   * @param text 链接文本
   * @param href 链接地址
   * @param target 目标窗口（默认 '_blank'）
   * @param rel 链接关系（默认 'noopener noreferrer'）
   * @returns 链接元素字符串
   */
  static generateLinkElement(text: string, href: string, target: string = '_blank', rel: string = 'noopener noreferrer'): string {
    return `<a href="${href}" target="${target}" rel="${rel}">${text}</a>`
  }

  /**
   * 从元素文本列表中提取最大数字
   * @param $items 元素列表
   * @returns 最大数字
   */
  static extractMaxNumberFromElements($items: JQuery<HTMLElement>): number {
    return $items.toArray().reduce((max, item) => {
      const num = Number(item.textContent)

      return num > max ? num : max
    }, 1)
  }

  /**
   * 转义选择器中的引号
   * @param selector 原始选择器字符串
   * @param targetQuoteType 目标引号类型，默认为单引号
   * @returns 转义后的选择器
   */
  static escapeSelectorQuotes(selector: string | undefined, targetQuoteType: 'single' | 'double' = 'single'): { data: string, escaped: boolean } {
    let data = selector?.trim() || ''
    let escaped: boolean = false

    if (data.includes('\'') && targetQuoteType === 'double') {
      // 转义为双引号：将单引号转义，双引号保持不变
      data = data.replace(/'/g, '\\\'')
      escaped = true
    }
    else if (data.includes('"') && targetQuoteType === 'single') {
      // 转义为单引号：将双引号转义，单引号保持不变
      data = data.replace(/"/g, '\'')
      escaped = true
    }

    return {
      data,
      escaped,
    }
  }

  /**
   * 拼接属性选择器
   * @param baseSelector 基础选择器
   * @param attribute 属性名
   * @param value 属性值
   * @returns 拼接后的选择器
   */
  static concatAttributeSelector(
    baseSelector: string,
    attribute: string,
    value?: string | number,
  ): string {
    const suffix = value !== undefined
      ? `[${attribute}="${value}"]`
      : `[${attribute}]`

    return this.concatSelector(baseSelector, suffix)
  }

  /**
   * 拼接选择器
   * @param baseSelector 基础选择器（可能包含逗号）
   * @param suffix 要拼接的后缀
   * @returns 拼接后的选择器数组
   */
  static concatSelector(baseSelector: string, suffix: string): string {
    if (!baseSelector || !suffix) {
      return [baseSelector || ''].join(', ')
    }

    // 按逗号分割选择器
    const baseParts = baseSelector.split(',').map(part => part.trim()).filter(Boolean)

    // 为每个部分拼接后缀
    return baseParts.map((part) => {
      // 处理已经包含后缀的情况
      if (part.includes(suffix))
        return part

      // 安全拼接（处理空格）
      if (part.endsWith(']')) {
        // 如果是属性选择器，在属性前插入后缀
        const lastBracketIndex = part.lastIndexOf('[')

        if (lastBracketIndex > 0) {
          const beforeBracket = part.substring(0, lastBracketIndex).trim()
          const afterBracket = part.substring(lastBracketIndex)
          return `${beforeBracket}${suffix} ${afterBracket}`
        }
      }

      // 普通拼接
      return `${part}${suffix}`
    }).join(', ')
  }

  /**
   * 查询元素并统一返回 NodeListOf<Element>
   */
  static queryElements(
    selector: string,
    multiple: boolean,
  ): NodeListOf<Element> {
    if (multiple)
      return document.querySelectorAll(selector)

    const element = document.querySelector(selector)
    return element ? this.createNodeListFromElement(element) : this.createEmptyNodeList()
  }

  /**
   * 使用临时容器将单个元素包装为 NodeListOf<Element>
   */
  static createNodeListFromElement(element: Element): NodeListOf<Element> {
    const container = document.createElement('div')
    container.appendChild(element.cloneNode(true))
    return container.childNodes as NodeListOf<Element>
  }

  /**
   * 创建空 NodeList
   */
  static createEmptyNodeList(): NodeListOf<Element> {
    return document.querySelectorAll('*')
  }

  /**
   * 计算元素旁提示框的智能定位（根据元素位置和屏幕边界自动调整，避免提示框超出可视区域）
   */
  static calculateTooltipPosition($element: JQuery<HTMLElement>, maxWidth: number = 400): { left: string, top: string } {
    const elementRect = $element[0].getBoundingClientRect()
    const scrollX = window.scrollX || window.pageXOffset
    const scrollY = window.scrollY || window.pageYOffset

    // 默认显示在元素右侧
    let left = elementRect.right + scrollX + 10
    let top = elementRect.top + scrollY

    // 检查右侧空间是否足够，不足则显示在左侧
    if (left + maxWidth > window.innerWidth + scrollX) {
      left = elementRect.left + scrollX - maxWidth - 10
    }

    // 检查底部空间是否足够，不足则向上调整
    if (top + maxWidth > window.innerHeight + scrollY) {
      top = window.innerHeight + scrollY - maxWidth - 10
    }

    return {
      left: `${left}px`,
      top: `${top}px`,
    }
  }

  /**
   * 判断元素文本是否多行
   * @param element 目标元素
   * @returns 是否多行
   */
  static isMultilineText(element: HTMLElement | null): boolean {
    if (!element || !element.textContent?.trim())
      return false

    return this.checkByRangeAPI(element)
  }

  /**
   * 使用 Range API 精确判断
   */
  private static checkByRangeAPI(element: HTMLElement): boolean {
    try {
      const textNodes = Array.from(element.childNodes).filter(
        node => node.nodeType === Node.TEXT_NODE && node.textContent?.trim(),
      ) as Text[]

      if (textNodes.length === 0)
        return false

      const range = document.createRange()
      const firstNode = textNodes[0]
      const lastNode = textNodes[textNodes.length - 1]

      range.setStart(firstNode, 0)
      range.setEnd(firstNode, 1)
      const firstRect = range.getBoundingClientRect()

      range.setStart(lastNode, Math.max(0, lastNode.length - 1))
      range.setEnd(lastNode, lastNode.length)
      const lastRect = range.getBoundingClientRect()

      return Math.abs(firstRect.top - lastRect.top) > 2
    }
    catch {
      return false
    }
  }
}
